Introduction & Set up

Chicago’s crime rates have long been a subject of national and international concern and the city itself serves as a microcosm of urban crime challenges faced by major metropolitan areas worldwide. By examining the patterns and determinants of robbery in Chicago, the dynamics of criminal activities can be understood better, which provides valuable insights into broader issues such as social inequality, policing strategies, and community safety.

Selection bias happen when places that have suffered from robbery incidents historically are likely to be predicted to have higher robbery rates than usual. This may due to complicated reasons. Using a dataset for specific year for analysis can introduce bias, as robbery patterns can vary over time. If the chosen period doesn’t capture these variations, the study results may not be generalizable. Other factors like the survivorship bias in data recording may also result in selection bias in modeling. The bias can lead to an underestimation or overestimation of the risk of robbery in certain areas, which can lead to an incomplete and potentially skewed understanding of the factors contributing to robbery and its spatial or temporal patterns in Chicago. Furthermore, public policy and law enforcement strategies may be misinformed if selection bias leads to inaccurate insights into high-risk areas or demographics.

\(All data resource from Chicago Open Data site (https://data.cityofchicago.org/).\)

knitr::opts_chunk$set(
    echo = TRUE,
    message = FALSE,
    warning = FALSE
)
library(tidyverse)
library(sf)
library(RSocrata)
library(viridis)
library(spatstat.explore)
library(raster)
library(spdep)
library(FNN)
library(grid)
library(gridExtra)
library(knitr)
library(kableExtra)
library(tidycensus)
library(classInt)   # for KDE and ML risk class intervals
# functions
root.dir = "https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/DATA/"
source("https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/functions.r")

Data Wrangling

Base Data

Loading and Visualizing Chicago Data

Base data are about the geographic and crime data in Chicago. Point plot and density plot are used fot the visualization of police districts, police beats, Chicago city boundary, and robbery data as follows.

policeDistricts <- 
  st_read("https://data.cityofchicago.org/api/geospatial/fthy-xz3r?method=export&format=GeoJSON") %>%
  st_transform('ESRI:102271') %>%
  dplyr::select(District = dist_num)
  
policeBeats <- 
  st_read("https://data.cityofchicago.org/api/geospatial/aerh-rz74?method=export&format=GeoJSON") %>%
  st_transform('ESRI:102271') %>%
  dplyr::select(District = beat_num)

bothPoliceUnits <- rbind(mutate(policeDistricts, Legend = "Police Districts"), 
                         mutate(policeBeats, Legend = "Police Beats"))

robbery <- 
  read.socrata("https://data.cityofchicago.org/Public-Safety/Crimes-2020/qzdf-xmn8") %>% 
    filter(Primary.Type == "ROBBERY" & Description == "ARMED - HANDGUN") %>%
    mutate(x = gsub("[()]", "", Location)) %>%
    separate(x,into= c("Y","X"), sep=",") %>%
    mutate(X = as.numeric(X),Y = as.numeric(Y)) %>% 
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant")%>%
    st_transform('ESRI:102271') %>% 
    distinct()

chicagoBoundary <- 
  st_read(file.path(root.dir,"/Chapter5/chicagoBoundary.geojson")) %>%
  st_transform('ESRI:102271') 
grid.arrange(ncol=2,
ggplot() + 
  geom_sf(data = chicagoBoundary) +
  geom_sf(data = robbery, colour="#c44536", size=0.1, show.legend = "point") +
  labs(title= "Robbery",
       subtitle = "Chicago - 2020") +
  mapTheme(title_size = 14),

ggplot() + 
  geom_sf(data = chicagoBoundary, fill = "grey80") +
  stat_density2d(data = data.frame(st_coordinates(robbery)), 
                 aes(X, Y, fill = ..level.., alpha = ..level..),
                 size = 0.01, bins = 40, geom = 'polygon') +
  scale_fill_viridis() +
  scale_alpha(range = c(0.00, 0.35), guide = FALSE) +
  labs(title = "Density of Robbery",
       subtitle = "Chicago - 2020") +
  mapTheme(title_size = 14) + theme(legend.position = "none"))

Robbery Fishnet Plot

Robbery fishnet plot has been created according to the robbery data. It can be seen clearly that most robberies happen in the northwest and south of the city.

fishnet <- 
  st_make_grid(chicagoBoundary,
               cellsize = 500, 
               square = TRUE) %>%
  .[chicagoBoundary] %>%            
  st_sf() %>%
  mutate(uniqueID = 1:n())

crime_net <- 
  dplyr::select(robbery) %>% 
  mutate(countRobbery = 1) %>% 
  aggregate(., fishnet, sum) %>%
  mutate(countRobbery = replace_na(countRobbery, 0),
         uniqueID = 1:n(),
         cvID = sample(round(nrow(fishnet) / 24), 
                       size=nrow(fishnet), replace = TRUE))

ggplot() +
  geom_sf(data = crime_net, aes(fill = countRobbery), color = NA) +
  scale_fill_viridis() +
  labs(title = "Count of Robbery for the Fishnet",
       subtitle = "Chicago - 2020") +
  mapTheme()

Additional Spatial Features

Adding Other Spatial Features

Spatial features such as abandoned cars, abandoned buildings, shot spotter alerts, street lights condition, and traffic crash are selected to explore the relationship of these city features and crime rate.

abandonCars <- 
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Abandoned-Vehicles/3c9v-pnva") %>%
    mutate(year = substr(creation_date,1,4)) %>% filter(year == "2017") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Abandoned_Cars")

abandonBuildings <- 
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Vacant-and-Abandoned-Building/7nii-7srd") %>%
    mutate(year = substr(date_service_request_was_received,1,4)) %>%  filter(year == "2017") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Abandoned_Buildings")

shotspotterAlerts <- 
  read.socrata("https://data.cityofchicago.org/Public-Safety/Violence-Reduction-Shotspotter-Alerts/3h7q-7mdb") %>%
    mutate(year = substr(date,1,4)) %>% filter(year == "2017") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Shotspotter_Alerts")

streetlightsOut <- 
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Street-Lights-All-Out/zuxi-7xem") %>%
  mutate(year = substr(creation_date,1,4)) %>% filter(year == "2017") %>%
  dplyr::select(Y = latitude, X = longitude) %>%
  na.omit() %>%
  st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
  st_transform(st_crs(fishnet)) %>%
  mutate(Legend = "Street_Lights_Out")

TrafficCrash <- 
  read.socrata("https://data.cityofchicago.org/Transportation/Traffic-Crashes-Crashes/85ca-t3if") %>%
  mutate(year = substr(crash_date,1,4)) %>% filter(year == "2017") %>%
  dplyr::select(Y = latitude, X = longitude) %>%
  na.omit() %>%
  st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
  st_transform(st_crs(fishnet)) %>%
  mutate(Legend = "Traffic_Crash")

neighborhoods <- 
  st_read("https://raw.githubusercontent.com/blackmad/neighborhoods/master/chicago.geojson") %>%
  st_transform(st_crs(fishnet)) 

Fishnet Risk Features

Fishnet plots are also used to show the distribution and density of the spatial features. Most abandoned buildings concentrate in the southern part of Chicago, and others are in the northwest. The shot spotter alert has similar distribution, but the coverage is much smaller. Most abandoned cars are in north and west, and there’s no clear trend of street lights out shown in the plot. The distributions of both abandoned cars and street lights out are more scattered compared to the former two features.

vars_net <- 
  rbind(abandonCars, abandonBuildings, streetlightsOut, shotspotterAlerts, TrafficCrash) %>%
  st_join(fishnet, join=st_within) %>%
  st_drop_geometry() %>%
  group_by(uniqueID, Legend) %>%
  summarize(count = n()) %>%
  left_join(fishnet, ., by = "uniqueID") %>%
  spread(Legend, count, fill=0) %>%
  dplyr::select(-`<NA>`) %>%
  na.omit() %>%
  ungroup()

vars_net.long <- 
  gather(vars_net, Variable, value, -geometry, -uniqueID)

vars <- unique(vars_net.long$Variable)
mapList <- list()

for(i in vars){
  mapList[[i]] <- 
    ggplot() +
      geom_sf(data = filter(vars_net.long, Variable == i), aes(fill=value), colour=NA) +
      scale_fill_viridis(name="") +
      labs(title=i) +
      mapTheme()}

do.call(grid.arrange,c(mapList, ncol=3,nrow=2, top="Risk Factors by Fishnet"))

Nearest Neighbor Features

By doing the nearest neighbor calculation, the distance of the nearest three features of each type can be visualized as follows. These results serve as important independent variables in the regression model.

st_c    <- st_coordinates
st_coid <- st_centroid

vars_net <- vars_net %>%
    mutate(Abandoned_Cars.nn = nn_function(st_c(st_coid(vars_net)), 
                                           st_c(abandonCars),
                                           k = 3),
           Abandoned_Buildings.nn = nn_function(st_c(st_coid(vars_net)), 
                                           st_c(abandonBuildings),
                                           k = 3),
           Street_Lights_Out.nn = nn_function(st_c(st_coid(vars_net)), 
                                           st_c(streetlightsOut),
                                           k = 3),
           Shot_Spotter_Alerts.nn = nn_function(st_c(st_coid(vars_net)), 
                                           st_c(shotspotterAlerts),
                                           k = 3),
           Traffic_Crash.nn = nn_function(st_c(st_coid(vars_net)),
                                          na.omit(st_c(TrafficCrash)),
                                          k = 3)
           )

vars_net.long.nn <- 
  dplyr::select(vars_net, ends_with(".nn")) %>%
    gather(Variable, value, -geometry)

vars <- unique(vars_net.long.nn$Variable)
mapList <- list()

for(i in vars){
  mapList[[i]] <- 
    ggplot() +
    geom_sf(data = filter(vars_net.long.nn, Variable == i), aes(fill=value), colour=NA) +
    scale_fill_viridis(name="") +
    labs(title=i) +
    mapTheme()}

do.call(grid.arrange,c(mapList, ncol = 3, top = "Nearest Neighbor risk Features by Fishnet"))

Join NN feature to our fishnet & Join in areal data

The following map shows the distribution of districts in Chicago.

final_net <-
  left_join(crime_net, st_drop_geometry(vars_net), by="uniqueID") 

final_net <-
  st_centroid(final_net) %>%
    st_join(dplyr::select(neighborhoods, name), by = "uniqueID") %>%
    st_join(dplyr::select(policeDistricts, District), by = "uniqueID") %>%
      st_drop_geometry() %>%
      left_join(dplyr::select(final_net, geometry, uniqueID)) %>%
      st_sf() %>%
  na.omit()

mapview::mapview(final_net, zcol = "District")

Spatial Correlation

Local Moran’s I

Local Moran’s I is calculated and the hotspots of robbery are identified according to the Moran’s I result. It can be seen from the plot below that there are some small significant clusters of robberies in Northwest and South Chicago. This again point out the importance to account for spatial features when predicting the robbery pattern.

final_net.nb <- poly2nb(as_Spatial(final_net), queen=TRUE)
final_net.weights <- nb2listw(final_net.nb, style="W", zero.policy=TRUE)

local_morans <- localmoran(final_net$countRobbery, final_net.weights, zero.policy=TRUE) %>% 
  as.data.frame()

final_net.localMorans <- 
  cbind(local_morans, as.data.frame(final_net)) %>% 
  st_sf() %>%
  dplyr::select(Robbery_count = countRobbery, 
                Local_Morans_I = Ii, 
                P_Value = `Pr(z != E(Ii))`) %>%
  mutate(Significant_Hotspots = ifelse(P_Value <= 0.001, 1, 0)) %>%
  gather(Variable, Value, -geometry)

vars <- unique(final_net.localMorans$Variable)
mapList <- list()

for(i in vars){
  mapList[[i]] <- 
    ggplot() +
      geom_sf(data = filter(final_net.localMorans, Variable == i), 
              aes(fill = Value), colour=NA) +
      scale_fill_viridis(name="") +
      labs(title=i) +
      mapTheme(title_size = 14) + theme(legend.position="bottom")}

do.call(grid.arrange,c(mapList, ncol = 2, top = "Local Morans I statistics, Robbery"))

Correlations Scatterplots and Histogram of Robbery Counts

The correlation test results are shown as follows. Some predictors, like abandoned buildings, shot spotter alerts, and traffic crash show moderate correlations, while the relationship of other features, like abandoned cars, with dependent variable seem to be weak. Meanwhile, the correlation of spatial features are positive, while the relationship between robbery and nearest neighbor features are negative.

The following histogram shows the distribution of robberies, which is like Poisson distribution.

correlation_long <- 
  st_drop_geometry(final_net)%>% 
  dplyr::select(-uniqueID, -cvID, -name, -District) %>%
  pivot_longer(cols = -countRobbery, # everything except measurement
               names_to = "Type", # categorizes all quantitative variables into Type
               values_to = "Number") # the name of values is Number

 correlation_long %>%
  ggplot(aes(x= Number, y = countRobbery)) +
  geom_point(size = 0.1, color = "#283d3b") +  
  geom_smooth(method='lm', formula= y~x, lwd=0.5, color = "#c44536") +
  facet_wrap(~ Type, scales = "free", labeller= labeller(Type = c(
    `Abandoned_Buildings` = "Abandoned Buildings",
    `Abandoned_Cars` = "Abandoned Cars",
    `Shotspotter_Alerts` = "Shotspotter Alerts",
    `Street_Lights_Out` = "Streetlights Out",
    `Traffic_Crash` = "Traffic Crash",
    `Abandoned_Buildings.nn` = "Abandoned Buildings.nn",
    `Abandoned_Cars.nn` = "Abandoned Cars.nn",
    `Shot_Spotter_Alerts.nn` = "Shotspotter Alerts.nn",
    `Street_Lights_Out.nn` = "Streetlights Out.nn",
    `Traffic_Crash.nn` = "Traffic Crash.nn"
    )))  +
  labs(title = "Scatter Plot of Robbery over Risk Features") +
  plotTheme()

 ggplot(correlation_long, aes(x = countRobbery)) +
  geom_histogram(binwidth = 1, fill = "#c44536", color = "#283d3b") +
  labs(
    title = "Histogram of Robbery Counts",
    x = "Robbery Counts",
    y = "Frequency"
  ) +
  theme_minimal()

Distance to Hot spot

Below is the distance to hotspot plot.

final_net <- final_net %>% 
  mutate(robbery.isSig = 
           ifelse(local_morans[,5] <= 0.001, 1, 0)) %>%
  mutate(robbery.isSig.dist = 
           nn_function(st_c(st_coid(final_net)),
                       st_c(st_coid(filter(final_net,
                                           robbery.isSig == 1))), 
                       k = 1))

ggplot() +
      geom_sf(data = final_net, aes(fill=robbery.isSig.dist), colour=NA) +
      scale_fill_viridis(name="NN Distance") +
      labs(title="Robbery NN Distance") +
      mapTheme()

Modeling

Fold and Spatial Regression

Just risk factors list contains only spatial variables, such as abandoned cars, abandoned buildings, shot spotter alerts, and street lights out. Based on this, spatial process list contains additional crimehotspots like robbery.isSig and robbery.isSig.dist. Flod and spatial regressions are done using these variable lists.

reg.vars <- c("Abandoned_Buildings.nn", "Abandoned_Cars.nn", "Street_Lights_Out.nn", "Traffic_Crash.nn", "Shot_Spotter_Alerts.nn")

reg.ss.vars <- c("Abandoned_Buildings.nn", "Abandoned_Cars.nn", "Street_Lights_Out.nn", "Traffic_Crash.nn", "Shot_Spotter_Alerts.nn", "robbery.isSig", "robbery.isSig.dist")

reg.cv <- crossValidate(
  dataset = final_net,
  id = "cvID",
  dependentVariable = "countRobbery",
  indVariables = reg.vars) %>%
  dplyr::select(cvID = cvID, countRobbery, Prediction, geometry)

reg.ss.cv <- crossValidate(
  dataset = final_net,
  id = "cvID",
  dependentVariable = "countRobbery",
  indVariables = reg.ss.vars) %>%
  dplyr::select(cvID = cvID, countRobbery, Prediction, geometry)

reg.spatialCV <- crossValidate(
  dataset = final_net,
  id = "name",
  dependentVariable = "countRobbery",
  indVariables = reg.vars) %>%
  dplyr::select(cvID = name, countRobbery, Prediction, geometry)

reg.ss.spatialCV <- crossValidate(
  dataset = final_net,
  id = "name",                           
  dependentVariable = "countRobbery",
  indVariables = reg.ss.vars) %>%
    dplyr::select(cvID = name, countRobbery, Prediction, geometry)

reg.summary <- 
  rbind(
    mutate(reg.cv, Error = Prediction - countRobbery,
           Regression = "Random k-fold CV: Just Risk Factors"),
    mutate(reg.ss.cv, Error = Prediction - countRobbery,
           Regression = "Random k-fold CV: Spatial Process"),
    mutate(reg.spatialCV, Error = Prediction - countRobbery,
           Regression = "Spatial LOGO-CV: Just Risk Factors"),
    mutate(reg.ss.spatialCV, Error = Prediction - countRobbery,
           Regression = "Spatial LOGO-CV: Spatial Process")
  ) %>% 
  st_sf()

Calculating Errors across space

The accuracy of the regression model is measured. Mean error, MAE, and SD MAE are shown in the maps, plots, and table below. It can be concluded that random k-fold model functions better than spatial LOGO model, and it seems that in the random k-fold model, spatial process model works more accurate than just risk factors model.

error_by_reg_and_fold <- 
  reg.summary %>%
    group_by(cvID, Regression) %>% 
    summarize(Mean_Error = mean(Prediction - countRobbery, na.rm = T),
              MAE = mean(abs(Mean_Error), na.rm = T),
              SD_MAE = mean(abs(Mean_Error), na.rm = T)) %>%
     ungroup()


vars <- unique(error_by_reg_and_fold$Regression)
mapList <- list()

for(i in vars){
  mapList[[i]] <- 
    ggplot() +
    geom_sf(data = filter(error_by_reg_and_fold, Regression == i), aes(fill=MAE), colour=NA) +
    scale_fill_viridis(name="") +
    labs(title=i, size = 10) +
    mapTheme()}

do.call(grid.arrange,c(mapList, ncol = 2, top = "MAE by Fold and Regression"))

error_by_reg_and_fold %>%
  ggplot(aes(MAE)) + 
  geom_histogram(bins = 30, colour="#283d3b", fill = "#c44536") +
  facet_wrap(~Regression) +  
  geom_vline(xintercept = 0) + scale_x_continuous(breaks = seq(0, 8, by = 1)) + 
  labs(title="Distribution of MAE", subtitle = "k-fold cross validation vs. LOGO-CV",
       x="Mean Absolute Error", y="Count") +
  plotTheme()

 error_by_reg_and_fold <- 
  reg.summary %>%
    group_by(Regression) %>% 
    summarize(Mean_Error = mean(Prediction - countRobbery, na.rm = T),
              MAE = mean(abs(Mean_Error), na.rm = T),
              SD_MAE = mean(abs(Mean_Error), na.rm = T)) %>%
     ungroup()
 
 error_by_reg_and_fold %>% 
 st_drop_geometry() %>%
  kable() %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>% 
  row_spec(2, color = "#283d3b", background = "#e9ecef") %>%
  row_spec(4, color = "#283d3b", background = "#e9ecef") 
Regression Mean_Error MAE SD_MAE
Random k-fold CV: Just Risk Factors 0.0001265 0.0001265 0.0001265
Random k-fold CV: Spatial Process 0.0002940 0.0002940 0.0002940
Spatial LOGO-CV: Just Risk Factors -0.0054445 0.0054445 0.0054445
Spatial LOGO-CV: Spatial Process -0.0064944 0.0064944 0.0064944

Errors by Race

The raw errors of model in race type are shown in the following table. Random k-fold model still performs better than spatial LOGO model in both majority white and majority non-white regions. However, the error of spatial process model is very close to just risk factors model in the comparison.

All the models show difference between majority white and majority non-whit in error. Compared to model with just risk factors, the errors of two regions are relevantly close to each other in model with spatial process, which may indicate a better generalizability.

reg.summary %>% 
  st_centroid() %>% 
  st_join(tracts17) %>%
  na.omit() %>%
  st_drop_geometry() %>%
  group_by(Regression, Race) %>%
  summarize(mean.Error = mean(Error, na.rm = T)) %>%
  spread(Race, mean.Error) %>%
  kable(caption = "Mean Error by neighborhood racial context") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))  %>% 
  row_spec(2, color = "#283d3b", background = "#e9ecef") %>%
  row_spec(4, color = "#283d3b", background = "#e9ecef") 
Mean Error by neighborhood racial context
Regression MajorityNonWhite MajorityWhite
Random k-fold CV: Just Risk Factors -0.1118125 0.1196218
Random k-fold CV: Spatial Process -0.0839205 0.0899487
Spatial LOGO-CV: Just Risk Factors -0.1259531 0.1231963
Spatial LOGO-CV: Spatial Process -0.1001091 0.0932068

Density vs predictions

The following plot is the kernel density of robberies in 2017. The regions with high kernel density have a higher probability of robberies in the next year.

rob_ppp <- as.ppp(st_coordinates(robbery), W = st_bbox(final_net))
rob_KD.1000 <- spatstat.explore::density.ppp(rob_ppp, 1000)

rob_KD.df <- rbind(
  mutate(data.frame(rasterToPoints(mask(raster(rob_KD.1000), as(neighborhoods, 'Spatial')))), Legend = "1000 Ft.")) 

rob_KD.df$Legend <- factor(rob_KD.df$Legend, levels = c("1000 Ft."))

as.data.frame(rob_KD.1000) %>%
  st_as_sf(coords = c("x", "y"), crs = st_crs(final_net)) %>%
  aggregate(., final_net, mean) %>%
   ggplot() +
     geom_sf(aes(fill=value)) +
     geom_sf(data = sample_n(robbery, 1500), size = .5, color = "#283d3b") +
     scale_fill_viridis(name = "Density") +
     labs(title = "Kernel Density of 2020 Robbery") +
     mapTheme(title_size = 14)

Compared to prediction made by kernel density analysis, the regression model makes a more accurate prediction on the geographic level. Regression model works more precisely in most parts of the city, especially the southern part, but seems to underestimate the robbery rank in north Chicago at the same time. Meanwhile, the result of kernel density analysis is more reliable in northeast area, where regression model fails to rank a higher level of robbery possibility.

robbery21 <- 
  read.socrata("https://data.cityofchicago.org/Public-Safety/Crimes-2021/dwme-t96c") %>% 
  filter(Primary.Type == "ROBBERY" & Description == "ARMED - HANDGUN") %>%
  mutate(x = gsub("[()]", "", Location)) %>%
  separate(x,into= c("Y","X"), sep=",") %>%
  mutate(X = as.numeric(X),
         Y = as.numeric(Y)) %>% 
  na.omit %>%
  st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
  st_transform('ESRI:102271') %>% 
  distinct() %>%
  .[fishnet,]

rob_KDE_sum <- as.data.frame(rob_KD.1000) %>%
  st_as_sf(coords = c("x", "y"), crs = st_crs(final_net)) %>%
  aggregate(., final_net, mean) 
kde_breaks <- classIntervals(rob_KDE_sum$value, 
                             n = 5, "fisher")
rob_KDE_sf <- rob_KDE_sum %>%
  mutate(label = "Kernel Density",
         Risk_Category = classInt::findCols(kde_breaks),
         Risk_Category = case_when(
           Risk_Category == 5 ~ "5th",
           Risk_Category == 4 ~ "4th",
           Risk_Category == 3 ~ "3rd",
           Risk_Category == 2 ~ "2nd",
           Risk_Category == 1 ~ "1st")) %>%
  cbind(
    aggregate(
      dplyr::select(robbery21) %>% mutate(robberyCount = 1), ., sum) %>%
    mutate(robberyCount = replace_na(robberyCount, 0))) %>%
  dplyr::select(label, Risk_Category, robberyCount)


ml_breaks <- classIntervals(reg.ss.spatialCV$Prediction, 
                             n = 5, "fisher")
rob_risk_sf <-
  reg.ss.spatialCV %>%
  mutate(label = "Risk Predictions",
         Risk_Category =classInt::findCols(ml_breaks),
         Risk_Category = case_when(
           Risk_Category == 5 ~ "5th",
           Risk_Category == 4 ~ "4th",
           Risk_Category == 3 ~ "3rd",
           Risk_Category == 2 ~ "2nd",
           Risk_Category == 1 ~ "1st")) %>%
  cbind(
    aggregate( 
      dplyr::select(robbery21) %>% mutate(robberyCount = 1), ., sum) %>%
      mutate(robberyCount = replace_na(robberyCount, 0))) %>%
  dplyr::select(label,Risk_Category, robberyCount)


rbind(rob_KDE_sf, rob_risk_sf) %>%
  na.omit() %>%
  gather(Variable, Value, -label, -Risk_Category, -geometry) %>%
  ggplot() +
    geom_sf(aes(fill = Risk_Category), colour = NA) +
    geom_sf(data = sample_n(robbery21, 3000, replace = TRUE), size = .5, colour = "#283d3b") +
    facet_wrap(~label, ) +
    scale_fill_viridis(discrete = TRUE) +
    labs(title="Comparison of Kernel Density and Risk Predictions",
         subtitle="2020 robbery risk predictions; 2021 robbery") +
    mapTheme(title_size = 14)

As can be seen in the chart below, there’s a significant difference between the result produced by kernel density analysis and the prediction made regression model. Kernel density analysis predicts more cases in high rank groups (3rd, 4th and 5th), especially in the 4th rank, while regression model predicts more cases in low rank groups (1st and 2nd), especially in the 1st rank. In general, kernel density analysis predicts a increase trend in robbery cases and coverage, as regression model suggests there should be a declined trend in 2021 on the contrary.

rbind(rob_KDE_sf, rob_risk_sf) %>%
  st_drop_geometry() %>%
  na.omit() %>%
  gather(Variable, Value, -label, -Risk_Category) %>%
  group_by(label, Risk_Category) %>%
  summarize(countRobbery = sum(Value)) %>%
  ungroup() %>%
  group_by(label) %>%
  mutate(Pcnt_of_test_set_crimes = countRobbery / sum(countRobbery)) %>%
    ggplot(aes(Risk_Category,Pcnt_of_test_set_crimes)) +
      geom_bar(aes(fill=label), position="dodge", stat="identity") +
      scale_fill_viridis(discrete = TRUE, name = "Model") +
      labs(title = "Risk prediction vs. Kernel density, 2021 robbery",
           y = "% of Test Set Robbery (per model)",
           x = "Risk Category") +
  theme_bw() +
      theme(axis.text.x = element_text(angle = 45, vjust = 0.5))

Conclusion

In this project, geospatial features are examined and included in the risk regression model to reveal the relationship between spatial features and the crime attempts, especially robbery, in Chicago. Although the predictions made by the model may not be precise enough in some particular areas, this projection can explain the current crime pattern in Chicago to some extent.

However, I will probably not recommend the regression model for production use. Firstly, taking the future prediction as an example, the performance of the regression model is not ideal in northern part of Chicago: compared to kernel density analysis, it notably underestimates robberies in the northeast. Moreover, the MAE and the standard deviation of MAE are too small, which may indicate that the regression model is overfitting. This will result in the decrease in generalizability, and thus producing more errors in prediction. Additionally, the selected independent variables can only partly represent some factors driving robberies in Chicago. Other economic and demographic factors are dismissed in this model, leading to an incomprehensive interpretation of robberies. Furthermore, the base dataset is only about 2017, which is not representative enough, and more data about other years should be taken into consideration to produce a more reliable model.

Due to these reasons, I would less likely to recommend this regression model for production use, but I will suggest that the model can be considered as a reference in fitting suitable model for police practice, since some variables in the regression model show significant relationships with robberies and the model makes a well prediction in some parts of Chicago in prediction.

LS0tCnRpdGxlOiAiUHJlZGljdGl2ZSBQb2xpY2luZyIKc3VidGl0bGU6ICJSb2JiZXJ5IFByZWRpY3Rpb24gTW9kZWwgaW4gQ2hpY2FnbyIKYXV0aG9yOiAiRSBDaGluIExpIgpkYXRlOiAiYDEwLzE1LzIwMjNgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBzaW1wbGV4CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogeWVzCi0tLQojIEludHJvZHVjdGlvbiAmIFNldCB1cAoKQ2hpY2FnbydzIGNyaW1lIHJhdGVzIGhhdmUgbG9uZyBiZWVuIGEgc3ViamVjdCBvZiBuYXRpb25hbCBhbmQgaW50ZXJuYXRpb25hbCBjb25jZXJuIGFuZCB0aGUgY2l0eSBpdHNlbGYgc2VydmVzIGFzIGEgbWljcm9jb3NtIG9mIHVyYmFuIGNyaW1lIGNoYWxsZW5nZXMgZmFjZWQgYnkgbWFqb3IgbWV0cm9wb2xpdGFuIGFyZWFzIHdvcmxkd2lkZS4gQnkgZXhhbWluaW5nIHRoZSBwYXR0ZXJucyBhbmQgZGV0ZXJtaW5hbnRzIG9mIHJvYmJlcnkgaW4gQ2hpY2FnbywgdGhlIGR5bmFtaWNzIG9mIGNyaW1pbmFsIGFjdGl2aXRpZXMgY2FuIGJlIHVuZGVyc3Rvb2QgYmV0dGVyLCB3aGljaCBwcm92aWRlcyB2YWx1YWJsZSBpbnNpZ2h0cyBpbnRvIGJyb2FkZXIgaXNzdWVzIHN1Y2ggYXMgc29jaWFsIGluZXF1YWxpdHksIHBvbGljaW5nIHN0cmF0ZWdpZXMsIGFuZCBjb21tdW5pdHkgc2FmZXR5LgoKU2VsZWN0aW9uIGJpYXMgaGFwcGVuIHdoZW4gcGxhY2VzIHRoYXQgaGF2ZSBzdWZmZXJlZCBmcm9tIHJvYmJlcnkgaW5jaWRlbnRzIGhpc3RvcmljYWxseSBhcmUgbGlrZWx5IHRvIGJlIHByZWRpY3RlZCB0byBoYXZlIGhpZ2hlciByb2JiZXJ5IHJhdGVzIHRoYW4gdXN1YWwuIFRoaXMgbWF5IGR1ZSB0byBjb21wbGljYXRlZCByZWFzb25zLiBVc2luZyBhIGRhdGFzZXQgZm9yIHNwZWNpZmljIHllYXIgZm9yIGFuYWx5c2lzIGNhbiBpbnRyb2R1Y2UgYmlhcywgYXMgcm9iYmVyeSBwYXR0ZXJucyBjYW4gdmFyeSBvdmVyIHRpbWUuIElmIHRoZSBjaG9zZW4gcGVyaW9kIGRvZXNuJ3QgY2FwdHVyZSB0aGVzZSB2YXJpYXRpb25zLCB0aGUgc3R1ZHkgcmVzdWx0cyBtYXkgbm90IGJlIGdlbmVyYWxpemFibGUuIE90aGVyIGZhY3RvcnMgbGlrZSB0aGUgc3Vydml2b3JzaGlwIGJpYXMgaW4gZGF0YSByZWNvcmRpbmcgbWF5IGFsc28gcmVzdWx0IGluIHNlbGVjdGlvbiBiaWFzIGluIG1vZGVsaW5nLiBUaGUgYmlhcyBjYW4gbGVhZCB0byBhbiB1bmRlcmVzdGltYXRpb24gb3Igb3ZlcmVzdGltYXRpb24gb2YgdGhlIHJpc2sgb2Ygcm9iYmVyeSBpbiBjZXJ0YWluIGFyZWFzLCB3aGljaCBjYW4gbGVhZCB0byBhbiBpbmNvbXBsZXRlIGFuZCBwb3RlbnRpYWxseSBza2V3ZWQgdW5kZXJzdGFuZGluZyBvZiB0aGUgZmFjdG9ycyBjb250cmlidXRpbmcgdG8gcm9iYmVyeSBhbmQgaXRzIHNwYXRpYWwgb3IgdGVtcG9yYWwgcGF0dGVybnMgaW4gQ2hpY2Fnby4gRnVydGhlcm1vcmUsIHB1YmxpYyBwb2xpY3kgYW5kIGxhdyBlbmZvcmNlbWVudCBzdHJhdGVnaWVzIG1heSBiZSBtaXNpbmZvcm1lZCBpZiBzZWxlY3Rpb24gYmlhcyBsZWFkcyB0byBpbmFjY3VyYXRlIGluc2lnaHRzIGludG8gaGlnaC1yaXNrIGFyZWFzIG9yIGRlbW9ncmFwaGljcy4KCiRBbGwgZGF0YSByZXNvdXJjZSBmcm9tIENoaWNhZ28gT3BlbiBEYXRhIHNpdGUgKGh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy8pLiQKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKCWVjaG8gPSBUUlVFLAoJbWVzc2FnZSA9IEZBTFNFLAoJd2FybmluZyA9IEZBTFNFCikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkoUlNvY3JhdGEpCmxpYnJhcnkodmlyaWRpcykKbGlicmFyeShzcGF0c3RhdC5leHBsb3JlKQpsaWJyYXJ5KHJhc3RlcikKbGlicmFyeShzcGRlcCkKbGlicmFyeShGTk4pCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KGNsYXNzSW50KSAgICMgZm9yIEtERSBhbmQgTUwgcmlzayBjbGFzcyBpbnRlcnZhbHMKIyBmdW5jdGlvbnMKcm9vdC5kaXIgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9EQVRBLyIKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsL1B1YmxpYy1Qb2xpY3ktQW5hbHl0aWNzLUxhbmRpbmcvbWFzdGVyL2Z1bmN0aW9ucy5yIikKCmBgYAoKIyBEYXRhIFdyYW5nbGluZwoKIyMgQmFzZSBEYXRhCgojIyMgTG9hZGluZyBhbmQgVmlzdWFsaXppbmcgQ2hpY2FnbyBEYXRhCgpCYXNlIGRhdGEgYXJlIGFib3V0IHRoZSBnZW9ncmFwaGljIGFuZCBjcmltZSBkYXRhIGluIENoaWNhZ28uIFBvaW50IHBsb3QgYW5kIGRlbnNpdHkgcGxvdCBhcmUgdXNlZCBmb3QgdGhlIHZpc3VhbGl6YXRpb24gb2YgcG9saWNlIGRpc3RyaWN0cywgcG9saWNlIGJlYXRzLCBDaGljYWdvIGNpdHkgYm91bmRhcnksIGFuZCByb2JiZXJ5IGRhdGEgYXMgZm9sbG93cy4KYGBge3IgZGF0YWxvYWRpbmcsIHJlc3VsdHMgPSAnaGlkZSd9CnBvbGljZURpc3RyaWN0cyA8LSAKICBzdF9yZWFkKCJodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvYXBpL2dlb3NwYXRpYWwvZnRoeS14ejNyP21ldGhvZD1leHBvcnQmZm9ybWF0PUdlb0pTT04iKSAlPiUKICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyMjcxJykgJT4lCiAgZHBseXI6OnNlbGVjdChEaXN0cmljdCA9IGRpc3RfbnVtKQogIApwb2xpY2VCZWF0cyA8LSAKICBzdF9yZWFkKCJodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvYXBpL2dlb3NwYXRpYWwvYWVyaC1yejc0P21ldGhvZD1leHBvcnQmZm9ybWF0PUdlb0pTT04iKSAlPiUKICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyMjcxJykgJT4lCiAgZHBseXI6OnNlbGVjdChEaXN0cmljdCA9IGJlYXRfbnVtKQoKYm90aFBvbGljZVVuaXRzIDwtIHJiaW5kKG11dGF0ZShwb2xpY2VEaXN0cmljdHMsIExlZ2VuZCA9ICJQb2xpY2UgRGlzdHJpY3RzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKHBvbGljZUJlYXRzLCBMZWdlbmQgPSAiUG9saWNlIEJlYXRzIikpCgpyb2JiZXJ5IDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1B1YmxpYy1TYWZldHkvQ3JpbWVzLTIwMjAvcXpkZi14bW44IikgJT4lIAogICAgZmlsdGVyKFByaW1hcnkuVHlwZSA9PSAiUk9CQkVSWSIgJiBEZXNjcmlwdGlvbiA9PSAiQVJNRUQgLSBIQU5ER1VOIikgJT4lCiAgICBtdXRhdGUoeCA9IGdzdWIoIlsoKV0iLCAiIiwgTG9jYXRpb24pKSAlPiUKICAgIHNlcGFyYXRlKHgsaW50bz0gYygiWSIsIlgiKSwgc2VwPSIsIikgJT4lCiAgICBtdXRhdGUoWCA9IGFzLm51bWVyaWMoWCksWSA9IGFzLm51bWVyaWMoWSkpICU+JSAKICAgIG5hLm9taXQoKSAlPiUKICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSU+JQogICAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjI3MScpICU+JSAKICAgIGRpc3RpbmN0KCkKCmNoaWNhZ29Cb3VuZGFyeSA8LSAKICBzdF9yZWFkKGZpbGUucGF0aChyb290LmRpciwiL0NoYXB0ZXI1L2NoaWNhZ29Cb3VuZGFyeS5nZW9qc29uIikpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDIyNzEnKSAKYGBgCgpgYGB7ciByb2JiZXJ5LCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKZ3JpZC5hcnJhbmdlKG5jb2w9MiwKZ2dwbG90KCkgKyAKICBnZW9tX3NmKGRhdGEgPSBjaGljYWdvQm91bmRhcnkpICsKICBnZW9tX3NmKGRhdGEgPSByb2JiZXJ5LCBjb2xvdXI9IiNjNDQ1MzYiLCBzaXplPTAuMSwgc2hvdy5sZWdlbmQgPSAicG9pbnQiKSArCiAgbGFicyh0aXRsZT0gIlJvYmJlcnkiLAogICAgICAgc3VidGl0bGUgPSAiQ2hpY2FnbyAtIDIwMjAiKSArCiAgbWFwVGhlbWUodGl0bGVfc2l6ZSA9IDE0KSwKCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gY2hpY2Fnb0JvdW5kYXJ5LCBmaWxsID0gImdyZXk4MCIpICsKICBzdGF0X2RlbnNpdHkyZChkYXRhID0gZGF0YS5mcmFtZShzdF9jb29yZGluYXRlcyhyb2JiZXJ5KSksIAogICAgICAgICAgICAgICAgIGFlcyhYLCBZLCBmaWxsID0gLi5sZXZlbC4uLCBhbHBoYSA9IC4ubGV2ZWwuLiksCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDAuMDEsIGJpbnMgPSA0MCwgZ2VvbSA9ICdwb2x5Z29uJykgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsKICBzY2FsZV9hbHBoYShyYW5nZSA9IGMoMC4wMCwgMC4zNSksIGd1aWRlID0gRkFMU0UpICsKICBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgUm9iYmVyeSIsCiAgICAgICBzdWJ0aXRsZSA9ICJDaGljYWdvIC0gMjAyMCIpICsKICBtYXBUaGVtZSh0aXRsZV9zaXplID0gMTQpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSkKYGBgCgojIyMgUm9iYmVyeSBGaXNobmV0IFBsb3QKClJvYmJlcnkgZmlzaG5ldCBwbG90IGhhcyBiZWVuIGNyZWF0ZWQgYWNjb3JkaW5nIHRvIHRoZSByb2JiZXJ5IGRhdGEuIEl0IGNhbiBiZSBzZWVuIGNsZWFybHkgdGhhdCBtb3N0IHJvYmJlcmllcyBoYXBwZW4gaW4gdGhlIG5vcnRod2VzdCBhbmQgc291dGggb2YgdGhlIGNpdHkuCmBgYHtyIHJvYmJlcnkgZmlzaG5ldCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmlzaG5ldCA8LSAKICBzdF9tYWtlX2dyaWQoY2hpY2Fnb0JvdW5kYXJ5LAogICAgICAgICAgICAgICBjZWxsc2l6ZSA9IDUwMCwgCiAgICAgICAgICAgICAgIHNxdWFyZSA9IFRSVUUpICU+JQogIC5bY2hpY2Fnb0JvdW5kYXJ5XSAlPiUgICAgICAgICAgICAKICBzdF9zZigpICU+JQogIG11dGF0ZSh1bmlxdWVJRCA9IDE6bigpKQoKY3JpbWVfbmV0IDwtIAogIGRwbHlyOjpzZWxlY3Qocm9iYmVyeSkgJT4lIAogIG11dGF0ZShjb3VudFJvYmJlcnkgPSAxKSAlPiUgCiAgYWdncmVnYXRlKC4sIGZpc2huZXQsIHN1bSkgJT4lCiAgbXV0YXRlKGNvdW50Um9iYmVyeSA9IHJlcGxhY2VfbmEoY291bnRSb2JiZXJ5LCAwKSwKICAgICAgICAgdW5pcXVlSUQgPSAxOm4oKSwKICAgICAgICAgY3ZJRCA9IHNhbXBsZShyb3VuZChucm93KGZpc2huZXQpIC8gMjQpLCAKICAgICAgICAgICAgICAgICAgICAgICBzaXplPW5yb3coZmlzaG5ldCksIHJlcGxhY2UgPSBUUlVFKSkKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBjcmltZV9uZXQsIGFlcyhmaWxsID0gY291bnRSb2JiZXJ5KSwgY29sb3IgPSBOQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsKICBsYWJzKHRpdGxlID0gIkNvdW50IG9mIFJvYmJlcnkgZm9yIHRoZSBGaXNobmV0IiwKICAgICAgIHN1YnRpdGxlID0gIkNoaWNhZ28gLSAyMDIwIikgKwogIG1hcFRoZW1lKCkKCmBgYAoKIyMgQWRkaXRpb25hbCBTcGF0aWFsIEZlYXR1cmVzCgojIyMgQWRkaW5nIE90aGVyIFNwYXRpYWwgRmVhdHVyZXMKClNwYXRpYWwgZmVhdHVyZXMgc3VjaCBhcyBhYmFuZG9uZWQgY2FycywgYWJhbmRvbmVkIGJ1aWxkaW5ncywgc2hvdCBzcG90dGVyIGFsZXJ0cywgc3RyZWV0IGxpZ2h0cyBjb25kaXRpb24sIGFuZCB0cmFmZmljIGNyYXNoIGFyZSBzZWxlY3RlZCB0byBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgb2YgdGhlc2UgY2l0eSBmZWF0dXJlcyBhbmQgY3JpbWUgcmF0ZS4KYGBge3IgZmVhdHVyZXMsIHJlc3VsdHMgPSAnaGlkZSd9CgphYmFuZG9uQ2FycyA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9TZXJ2aWNlLVJlcXVlc3RzLzMxMS1TZXJ2aWNlLVJlcXVlc3RzLUFiYW5kb25lZC1WZWhpY2xlcy8zYzl2LXBudmEiKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGNyZWF0aW9uX2RhdGUsMSw0KSkgJT4lIGZpbHRlcih5ZWFyID09ICIyMDE3IikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFkgPSBsYXRpdHVkZSwgWCA9IGxvbmdpdHVkZSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgICBzdF90cmFuc2Zvcm0oc3RfY3JzKGZpc2huZXQpKSAlPiUKICAgIG11dGF0ZShMZWdlbmQgPSAiQWJhbmRvbmVkX0NhcnMiKQoKYWJhbmRvbkJ1aWxkaW5ncyA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9TZXJ2aWNlLVJlcXVlc3RzLzMxMS1TZXJ2aWNlLVJlcXVlc3RzLVZhY2FudC1hbmQtQWJhbmRvbmVkLUJ1aWxkaW5nLzduaWktN3NyZCIpICU+JQogICAgbXV0YXRlKHllYXIgPSBzdWJzdHIoZGF0ZV9zZXJ2aWNlX3JlcXVlc3Rfd2FzX3JlY2VpdmVkLDEsNCkpICU+JSAgZmlsdGVyKHllYXIgPT0gIjIwMTciKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoWSA9IGxhdGl0dWRlLCBYID0gbG9uZ2l0dWRlKSAlPiUKICAgIG5hLm9taXQoKSAlPiUKICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSAlPiUKICAgIHN0X3RyYW5zZm9ybShzdF9jcnMoZmlzaG5ldCkpICU+JQogICAgbXV0YXRlKExlZ2VuZCA9ICJBYmFuZG9uZWRfQnVpbGRpbmdzIikKCnNob3RzcG90dGVyQWxlcnRzIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1B1YmxpYy1TYWZldHkvVmlvbGVuY2UtUmVkdWN0aW9uLVNob3RzcG90dGVyLUFsZXJ0cy8zaDdxLTdtZGIiKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGRhdGUsMSw0KSkgJT4lIGZpbHRlcih5ZWFyID09ICIyMDE3IikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFkgPSBsYXRpdHVkZSwgWCA9IGxvbmdpdHVkZSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgICBzdF90cmFuc2Zvcm0oc3RfY3JzKGZpc2huZXQpKSAlPiUKICAgIG11dGF0ZShMZWdlbmQgPSAiU2hvdHNwb3R0ZXJfQWxlcnRzIikKCnN0cmVldGxpZ2h0c091dCA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9TZXJ2aWNlLVJlcXVlc3RzLzMxMS1TZXJ2aWNlLVJlcXVlc3RzLVN0cmVldC1MaWdodHMtQWxsLU91dC96dXhpLTd4ZW0iKSAlPiUKICBtdXRhdGUoeWVhciA9IHN1YnN0cihjcmVhdGlvbl9kYXRlLDEsNCkpICU+JSBmaWx0ZXIoeWVhciA9PSAiMjAxNyIpICU+JQogIGRwbHlyOjpzZWxlY3QoWSA9IGxhdGl0dWRlLCBYID0gbG9uZ2l0dWRlKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogIHN0X3RyYW5zZm9ybShzdF9jcnMoZmlzaG5ldCkpICU+JQogIG11dGF0ZShMZWdlbmQgPSAiU3RyZWV0X0xpZ2h0c19PdXQiKQoKVHJhZmZpY0NyYXNoIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1RyYW5zcG9ydGF0aW9uL1RyYWZmaWMtQ3Jhc2hlcy1DcmFzaGVzLzg1Y2EtdDNpZiIpICU+JQogIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGNyYXNoX2RhdGUsMSw0KSkgJT4lIGZpbHRlcih5ZWFyID09ICIyMDE3IikgJT4lCiAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogIG5hLm9taXQoKSAlPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgbXV0YXRlKExlZ2VuZCA9ICJUcmFmZmljX0NyYXNoIikKCm5laWdoYm9yaG9vZHMgPC0gCiAgc3RfcmVhZCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2JsYWNrbWFkL25laWdoYm9yaG9vZHMvbWFzdGVyL2NoaWNhZ28uZ2VvanNvbiIpICU+JQogIHN0X3RyYW5zZm9ybShzdF9jcnMoZmlzaG5ldCkpIAoKYGBgCgojIyMgRmlzaG5ldCBSaXNrIEZlYXR1cmVzCgpGaXNobmV0IHBsb3RzIGFyZSBhbHNvIHVzZWQgdG8gc2hvdyB0aGUgZGlzdHJpYnV0aW9uIGFuZCBkZW5zaXR5IG9mIHRoZSBzcGF0aWFsIGZlYXR1cmVzLiBNb3N0IGFiYW5kb25lZCBidWlsZGluZ3MgY29uY2VudHJhdGUgaW4gdGhlIHNvdXRoZXJuIHBhcnQgb2YgQ2hpY2FnbywgYW5kIG90aGVycyBhcmUgaW4gdGhlIG5vcnRod2VzdC4gVGhlIHNob3Qgc3BvdHRlciBhbGVydCBoYXMgc2ltaWxhciBkaXN0cmlidXRpb24sIGJ1dCB0aGUgY292ZXJhZ2UgaXMgbXVjaCBzbWFsbGVyLiBNb3N0IGFiYW5kb25lZCBjYXJzIGFyZSBpbiBub3J0aCBhbmQgd2VzdCwgYW5kIHRoZXJlJ3Mgbm8gY2xlYXIgdHJlbmQgb2Ygc3RyZWV0IGxpZ2h0cyBvdXQgc2hvd24gaW4gdGhlIHBsb3QuIFRoZSBkaXN0cmlidXRpb25zIG9mIGJvdGggYWJhbmRvbmVkIGNhcnMgYW5kIHN0cmVldCBsaWdodHMgb3V0IGFyZSBtb3JlIHNjYXR0ZXJlZCBjb21wYXJlZCB0byB0aGUgZm9ybWVyIHR3byBmZWF0dXJlcy4KYGBge3IgZmlzaG5ldCByaXNrIGZlYXR1cmVzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKdmFyc19uZXQgPC0gCiAgcmJpbmQoYWJhbmRvbkNhcnMsIGFiYW5kb25CdWlsZGluZ3MsIHN0cmVldGxpZ2h0c091dCwgc2hvdHNwb3R0ZXJBbGVydHMsIFRyYWZmaWNDcmFzaCkgJT4lCiAgc3Rfam9pbihmaXNobmV0LCBqb2luPXN0X3dpdGhpbikgJT4lCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIGdyb3VwX2J5KHVuaXF1ZUlELCBMZWdlbmQpICU+JQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lCiAgbGVmdF9qb2luKGZpc2huZXQsIC4sIGJ5ID0gInVuaXF1ZUlEIikgJT4lCiAgc3ByZWFkKExlZ2VuZCwgY291bnQsIGZpbGw9MCkgJT4lCiAgZHBseXI6OnNlbGVjdCgtYDxOQT5gKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgdW5ncm91cCgpCgp2YXJzX25ldC5sb25nIDwtIAogIGdhdGhlcih2YXJzX25ldCwgVmFyaWFibGUsIHZhbHVlLCAtZ2VvbWV0cnksIC11bmlxdWVJRCkKCnZhcnMgPC0gdW5pcXVlKHZhcnNfbmV0LmxvbmckVmFyaWFibGUpCm1hcExpc3QgPC0gbGlzdCgpCgpmb3IoaSBpbiB2YXJzKXsKICBtYXBMaXN0W1tpXV0gPC0gCiAgICBnZ3Bsb3QoKSArCiAgICAgIGdlb21fc2YoZGF0YSA9IGZpbHRlcih2YXJzX25ldC5sb25nLCBWYXJpYWJsZSA9PSBpKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9IiIpICsKICAgICAgbGFicyh0aXRsZT1pKSArCiAgICAgIG1hcFRoZW1lKCl9Cgpkby5jYWxsKGdyaWQuYXJyYW5nZSxjKG1hcExpc3QsIG5jb2w9Myxucm93PTIsIHRvcD0iUmlzayBGYWN0b3JzIGJ5IEZpc2huZXQiKSkKCmBgYAoKIyMjIE5lYXJlc3QgTmVpZ2hib3IgRmVhdHVyZXMKCkJ5IGRvaW5nIHRoZSBuZWFyZXN0IG5laWdoYm9yIGNhbGN1bGF0aW9uLCB0aGUgZGlzdGFuY2Ugb2YgdGhlIG5lYXJlc3QgdGhyZWUgZmVhdHVyZXMgb2YgZWFjaCB0eXBlIGNhbiBiZSB2aXN1YWxpemVkIGFzIGZvbGxvd3MuIFRoZXNlIHJlc3VsdHMgc2VydmUgYXMgaW1wb3J0YW50IGluZGVwZW5kZW50IHZhcmlhYmxlcyBpbiB0aGUgcmVncmVzc2lvbiBtb2RlbC4KYGBge3Igbm4gZmVhdHVyZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpzdF9jICAgIDwtIHN0X2Nvb3JkaW5hdGVzCnN0X2NvaWQgPC0gc3RfY2VudHJvaWQKCnZhcnNfbmV0IDwtIHZhcnNfbmV0ICU+JQogICAgbXV0YXRlKEFiYW5kb25lZF9DYXJzLm5uID0gbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RfYyhhYmFuZG9uQ2FycyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gMyksCiAgICAgICAgICAgQWJhbmRvbmVkX0J1aWxkaW5ncy5ubiA9IG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0X2MoYWJhbmRvbkJ1aWxkaW5ncyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gMyksCiAgICAgICAgICAgU3RyZWV0X0xpZ2h0c19PdXQubm4gPSBubl9mdW5jdGlvbihzdF9jKHN0X2NvaWQodmFyc19uZXQpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdF9jKHN0cmVldGxpZ2h0c091dCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gMyksCiAgICAgICAgICAgU2hvdF9TcG90dGVyX0FsZXJ0cy5ubiA9IG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0X2Moc2hvdHNwb3R0ZXJBbGVydHMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgayA9IDMpLAogICAgICAgICAgIFRyYWZmaWNfQ3Jhc2gubm4gPSBubl9mdW5jdGlvbihzdF9jKHN0X2NvaWQodmFyc19uZXQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEub21pdChzdF9jKFRyYWZmaWNDcmFzaCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gMykKICAgICAgICAgICApCgp2YXJzX25ldC5sb25nLm5uIDwtIAogIGRwbHlyOjpzZWxlY3QodmFyc19uZXQsIGVuZHNfd2l0aCgiLm5uIikpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCB2YWx1ZSwgLWdlb21ldHJ5KQoKdmFycyA8LSB1bmlxdWUodmFyc19uZXQubG9uZy5ubiRWYXJpYWJsZSkKbWFwTGlzdCA8LSBsaXN0KCkKCmZvcihpIGluIHZhcnMpewogIG1hcExpc3RbW2ldXSA8LSAKICAgIGdncGxvdCgpICsKICAgIGdlb21fc2YoZGF0YSA9IGZpbHRlcih2YXJzX25ldC5sb25nLm5uLCBWYXJpYWJsZSA9PSBpKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lPSIiKSArCiAgICBsYWJzKHRpdGxlPWkpICsKICAgIG1hcFRoZW1lKCl9Cgpkby5jYWxsKGdyaWQuYXJyYW5nZSxjKG1hcExpc3QsIG5jb2wgPSAzLCB0b3AgPSAiTmVhcmVzdCBOZWlnaGJvciByaXNrIEZlYXR1cmVzIGJ5IEZpc2huZXQiKSkKYGBgCgojIyBKb2luIE5OIGZlYXR1cmUgdG8gb3VyIGZpc2huZXQgJiBKb2luIGluIGFyZWFsIGRhdGEKClRoZSBmb2xsb3dpbmcgbWFwIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgZGlzdHJpY3RzIGluIENoaWNhZ28uCmBgYHtyIGpvaW4gbm4gZmVhdHVyZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmluYWxfbmV0IDwtCiAgbGVmdF9qb2luKGNyaW1lX25ldCwgc3RfZHJvcF9nZW9tZXRyeSh2YXJzX25ldCksIGJ5PSJ1bmlxdWVJRCIpIAoKZmluYWxfbmV0IDwtCiAgc3RfY2VudHJvaWQoZmluYWxfbmV0KSAlPiUKICAgIHN0X2pvaW4oZHBseXI6OnNlbGVjdChuZWlnaGJvcmhvb2RzLCBuYW1lKSwgYnkgPSAidW5pcXVlSUQiKSAlPiUKICAgIHN0X2pvaW4oZHBseXI6OnNlbGVjdChwb2xpY2VEaXN0cmljdHMsIERpc3RyaWN0KSwgYnkgPSAidW5pcXVlSUQiKSAlPiUKICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogICAgICBsZWZ0X2pvaW4oZHBseXI6OnNlbGVjdChmaW5hbF9uZXQsIGdlb21ldHJ5LCB1bmlxdWVJRCkpICU+JQogICAgICBzdF9zZigpICU+JQogIG5hLm9taXQoKQoKbWFwdmlldzo6bWFwdmlldyhmaW5hbF9uZXQsIHpjb2wgPSAiRGlzdHJpY3QiKQpgYGAKCiMgU3BhdGlhbCBDb3JyZWxhdGlvbgoKIyMgTG9jYWwgTW9yYW4ncyBJIAoKTG9jYWwgTW9yYW7igJlzIEkgaXMgY2FsY3VsYXRlZCBhbmQgdGhlIGhvdHNwb3RzIG9mIHJvYmJlcnkgYXJlIGlkZW50aWZpZWQgYWNjb3JkaW5nIHRvIHRoZSBNb3JhbuKAmXMgSSByZXN1bHQuIEl0IGNhbiBiZSBzZWVuIGZyb20gdGhlIHBsb3QgYmVsb3cgdGhhdCB0aGVyZSBhcmUgc29tZSBzbWFsbCBzaWduaWZpY2FudCBjbHVzdGVycyBvZiByb2JiZXJpZXMgaW4gTm9ydGh3ZXN0IGFuZCBTb3V0aCBDaGljYWdvLiBUaGlzIGFnYWluIHBvaW50IG91dCB0aGUgaW1wb3J0YW5jZSB0byBhY2NvdW50IGZvciBzcGF0aWFsIGZlYXR1cmVzIHdoZW4gcHJlZGljdGluZyB0aGUgcm9iYmVyeSBwYXR0ZXJuLgpgYGB7ciBtb3Jhbiwgd2FybmluZz1GQUxTRX0KZmluYWxfbmV0Lm5iIDwtIHBvbHkybmIoYXNfU3BhdGlhbChmaW5hbF9uZXQpLCBxdWVlbj1UUlVFKQpmaW5hbF9uZXQud2VpZ2h0cyA8LSBuYjJsaXN0dyhmaW5hbF9uZXQubmIsIHN0eWxlPSJXIiwgemVyby5wb2xpY3k9VFJVRSkKCmxvY2FsX21vcmFucyA8LSBsb2NhbG1vcmFuKGZpbmFsX25ldCRjb3VudFJvYmJlcnksIGZpbmFsX25ldC53ZWlnaHRzLCB6ZXJvLnBvbGljeT1UUlVFKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpCgpmaW5hbF9uZXQubG9jYWxNb3JhbnMgPC0gCiAgY2JpbmQobG9jYWxfbW9yYW5zLCBhcy5kYXRhLmZyYW1lKGZpbmFsX25ldCkpICU+JSAKICBzdF9zZigpICU+JQogIGRwbHlyOjpzZWxlY3QoUm9iYmVyeV9jb3VudCA9IGNvdW50Um9iYmVyeSwgCiAgICAgICAgICAgICAgICBMb2NhbF9Nb3JhbnNfSSA9IElpLCAKICAgICAgICAgICAgICAgIFBfVmFsdWUgPSBgUHIoeiAhPSBFKElpKSlgKSAlPiUKICBtdXRhdGUoU2lnbmlmaWNhbnRfSG90c3BvdHMgPSBpZmVsc2UoUF9WYWx1ZSA8PSAwLjAwMSwgMSwgMCkpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1nZW9tZXRyeSkKCnZhcnMgPC0gdW5pcXVlKGZpbmFsX25ldC5sb2NhbE1vcmFucyRWYXJpYWJsZSkKbWFwTGlzdCA8LSBsaXN0KCkKCmZvcihpIGluIHZhcnMpewogIG1hcExpc3RbW2ldXSA8LSAKICAgIGdncGxvdCgpICsKICAgICAgZ2VvbV9zZihkYXRhID0gZmlsdGVyKGZpbmFsX25ldC5sb2NhbE1vcmFucywgVmFyaWFibGUgPT0gaSksIAogICAgICAgICAgICAgIGFlcyhmaWxsID0gVmFsdWUpLCBjb2xvdXI9TkEpICsKICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9IiIpICsKICAgICAgbGFicyh0aXRsZT1pKSArCiAgICAgIG1hcFRoZW1lKHRpdGxlX3NpemUgPSAxNCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpfQoKZG8uY2FsbChncmlkLmFycmFuZ2UsYyhtYXBMaXN0LCBuY29sID0gMiwgdG9wID0gIkxvY2FsIE1vcmFucyBJIHN0YXRpc3RpY3MsIFJvYmJlcnkiKSkKICAKYGBgCgojIyBDb3JyZWxhdGlvbnMgU2NhdHRlcnBsb3RzIGFuZCBIaXN0b2dyYW0gb2YgUm9iYmVyeSBDb3VudHMKClRoZSBjb3JyZWxhdGlvbiB0ZXN0IHJlc3VsdHMgYXJlIHNob3duIGFzIGZvbGxvd3MuIFNvbWUgcHJlZGljdG9ycywgbGlrZSBhYmFuZG9uZWQgYnVpbGRpbmdzLCBzaG90IHNwb3R0ZXIgYWxlcnRzLCBhbmQgdHJhZmZpYyBjcmFzaCBzaG93IG1vZGVyYXRlIGNvcnJlbGF0aW9ucywgd2hpbGUgdGhlIHJlbGF0aW9uc2hpcCBvZiBvdGhlciBmZWF0dXJlcywgbGlrZSBhYmFuZG9uZWQgY2Fycywgd2l0aCBkZXBlbmRlbnQgdmFyaWFibGUgc2VlbSB0byBiZSB3ZWFrLiBNZWFud2hpbGUsIHRoZSBjb3JyZWxhdGlvbiBvZiBzcGF0aWFsIGZlYXR1cmVzIGFyZSBwb3NpdGl2ZSwgd2hpbGUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHJvYmJlcnkgYW5kIG5lYXJlc3QgbmVpZ2hib3IgZmVhdHVyZXMgYXJlIG5lZ2F0aXZlLgoKVGhlIGZvbGxvd2luZyBoaXN0b2dyYW0gc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiByb2JiZXJpZXMsIHdoaWNoIGlzIGxpa2UgUG9pc3NvbiBkaXN0cmlidXRpb24uCmBgYHtyIHNjYXR0ZXIgcGxvdCBhbmQgaGlzdG9ncmFtLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmNvcnJlbGF0aW9uX2xvbmcgPC0gCiAgc3RfZHJvcF9nZW9tZXRyeShmaW5hbF9uZXQpJT4lIAogIGRwbHlyOjpzZWxlY3QoLXVuaXF1ZUlELCAtY3ZJRCwgLW5hbWUsIC1EaXN0cmljdCkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtY291bnRSb2JiZXJ5LCAjIGV2ZXJ5dGhpbmcgZXhjZXB0IG1lYXN1cmVtZW50CiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIlR5cGUiLCAjIGNhdGVnb3JpemVzIGFsbCBxdWFudGl0YXRpdmUgdmFyaWFibGVzIGludG8gVHlwZQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiTnVtYmVyIikgIyB0aGUgbmFtZSBvZiB2YWx1ZXMgaXMgTnVtYmVyCgogY29ycmVsYXRpb25fbG9uZyAlPiUKICBnZ3Bsb3QoYWVzKHg9IE51bWJlciwgeSA9IGNvdW50Um9iYmVyeSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjEsIGNvbG9yID0gIiMyODNkM2IiKSArICAKICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJywgZm9ybXVsYT0geX54LCBsd2Q9MC41LCBjb2xvciA9ICIjYzQ0NTM2IikgKwogIGZhY2V0X3dyYXAofiBUeXBlLCBzY2FsZXMgPSAiZnJlZSIsIGxhYmVsbGVyPSBsYWJlbGxlcihUeXBlID0gYygKICAgIGBBYmFuZG9uZWRfQnVpbGRpbmdzYCA9ICJBYmFuZG9uZWQgQnVpbGRpbmdzIiwKICAgIGBBYmFuZG9uZWRfQ2Fyc2AgPSAiQWJhbmRvbmVkIENhcnMiLAogICAgYFNob3RzcG90dGVyX0FsZXJ0c2AgPSAiU2hvdHNwb3R0ZXIgQWxlcnRzIiwKICAgIGBTdHJlZXRfTGlnaHRzX091dGAgPSAiU3RyZWV0bGlnaHRzIE91dCIsCiAgICBgVHJhZmZpY19DcmFzaGAgPSAiVHJhZmZpYyBDcmFzaCIsCiAgICBgQWJhbmRvbmVkX0J1aWxkaW5ncy5ubmAgPSAiQWJhbmRvbmVkIEJ1aWxkaW5ncy5ubiIsCiAgICBgQWJhbmRvbmVkX0NhcnMubm5gID0gIkFiYW5kb25lZCBDYXJzLm5uIiwKICAgIGBTaG90X1Nwb3R0ZXJfQWxlcnRzLm5uYCA9ICJTaG90c3BvdHRlciBBbGVydHMubm4iLAogICAgYFN0cmVldF9MaWdodHNfT3V0Lm5uYCA9ICJTdHJlZXRsaWdodHMgT3V0Lm5uIiwKICAgIGBUcmFmZmljX0NyYXNoLm5uYCA9ICJUcmFmZmljIENyYXNoLm5uIgogICAgKSkpICArCiAgbGFicyh0aXRsZSA9ICJTY2F0dGVyIFBsb3Qgb2YgUm9iYmVyeSBvdmVyIFJpc2sgRmVhdHVyZXMiKSArCiAgcGxvdFRoZW1lKCkKIAogZ2dwbG90KGNvcnJlbGF0aW9uX2xvbmcsIGFlcyh4ID0gY291bnRSb2JiZXJ5KSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgZmlsbCA9ICIjYzQ0NTM2IiwgY29sb3IgPSAiIzI4M2QzYiIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiSGlzdG9ncmFtIG9mIFJvYmJlcnkgQ291bnRzIiwKICAgIHggPSAiUm9iYmVyeSBDb3VudHMiLAogICAgeSA9ICJGcmVxdWVuY3kiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgpgYGAKCiMjIERpc3RhbmNlIHRvIEhvdCBzcG90CgpCZWxvdyBpcyB0aGUgZGlzdGFuY2UgdG8gaG90c3BvdCBwbG90LgpgYGB7ciBkaXN0YW5jZSB0byBob3Qgc3BvdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmZpbmFsX25ldCA8LSBmaW5hbF9uZXQgJT4lIAogIG11dGF0ZShyb2JiZXJ5LmlzU2lnID0gCiAgICAgICAgICAgaWZlbHNlKGxvY2FsX21vcmFuc1ssNV0gPD0gMC4wMDEsIDEsIDApKSAlPiUKICBtdXRhdGUocm9iYmVyeS5pc1NpZy5kaXN0ID0gCiAgICAgICAgICAgbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKGZpbmFsX25ldCkpLAogICAgICAgICAgICAgICAgICAgICAgIHN0X2Moc3RfY29pZChmaWx0ZXIoZmluYWxfbmV0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm9iYmVyeS5pc1NpZyA9PSAxKSkpLCAKICAgICAgICAgICAgICAgICAgICAgICBrID0gMSkpCgpnZ3Bsb3QoKSArCiAgICAgIGdlb21fc2YoZGF0YSA9IGZpbmFsX25ldCwgYWVzKGZpbGw9cm9iYmVyeS5pc1NpZy5kaXN0KSwgY29sb3VyPU5BKSArCiAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lPSJOTiBEaXN0YW5jZSIpICsKICAgICAgbGFicyh0aXRsZT0iUm9iYmVyeSBOTiBEaXN0YW5jZSIpICsKICAgICAgbWFwVGhlbWUoKQpgYGAKCiMjIE1vZGVsaW5nCgojIyMgRm9sZCBhbmQgU3BhdGlhbCBSZWdyZXNzaW9uCgpKdXN0IHJpc2sgZmFjdG9ycyBsaXN0IGNvbnRhaW5zIG9ubHkgc3BhdGlhbCB2YXJpYWJsZXMsIHN1Y2ggYXMgYWJhbmRvbmVkIGNhcnMsIGFiYW5kb25lZCBidWlsZGluZ3MsIHNob3Qgc3BvdHRlciBhbGVydHMsIGFuZCBzdHJlZXQgbGlnaHRzIG91dC4gQmFzZWQgb24gdGhpcywgc3BhdGlhbCBwcm9jZXNzIGxpc3QgY29udGFpbnMgYWRkaXRpb25hbCBjcmltZWhvdHNwb3RzIGxpa2UgYHJvYmJlcnkuaXNTaWdgIGFuZCBgcm9iYmVyeS5pc1NpZy5kaXN0YC4gRmxvZCBhbmQgc3BhdGlhbCByZWdyZXNzaW9ucyBhcmUgZG9uZSB1c2luZyB0aGVzZSB2YXJpYWJsZSBsaXN0cy4KYGBge3IgZm9sZCBhbmQgc3BhdGlhbCByZWdyZXNzaW9uLCByZXN1bHRzID0gJ2hpZGUnfQpyZWcudmFycyA8LSBjKCJBYmFuZG9uZWRfQnVpbGRpbmdzLm5uIiwgIkFiYW5kb25lZF9DYXJzLm5uIiwgIlN0cmVldF9MaWdodHNfT3V0Lm5uIiwgIlRyYWZmaWNfQ3Jhc2gubm4iLCAiU2hvdF9TcG90dGVyX0FsZXJ0cy5ubiIpCgpyZWcuc3MudmFycyA8LSBjKCJBYmFuZG9uZWRfQnVpbGRpbmdzLm5uIiwgIkFiYW5kb25lZF9DYXJzLm5uIiwgIlN0cmVldF9MaWdodHNfT3V0Lm5uIiwgIlRyYWZmaWNfQ3Jhc2gubm4iLCAiU2hvdF9TcG90dGVyX0FsZXJ0cy5ubiIsICJyb2JiZXJ5LmlzU2lnIiwgInJvYmJlcnkuaXNTaWcuZGlzdCIpCgpyZWcuY3YgPC0gY3Jvc3NWYWxpZGF0ZSgKICBkYXRhc2V0ID0gZmluYWxfbmV0LAogIGlkID0gImN2SUQiLAogIGRlcGVuZGVudFZhcmlhYmxlID0gImNvdW50Um9iYmVyeSIsCiAgaW5kVmFyaWFibGVzID0gcmVnLnZhcnMpICU+JQogIGRwbHlyOjpzZWxlY3QoY3ZJRCA9IGN2SUQsIGNvdW50Um9iYmVyeSwgUHJlZGljdGlvbiwgZ2VvbWV0cnkpCgpyZWcuc3MuY3YgPC0gY3Jvc3NWYWxpZGF0ZSgKICBkYXRhc2V0ID0gZmluYWxfbmV0LAogIGlkID0gImN2SUQiLAogIGRlcGVuZGVudFZhcmlhYmxlID0gImNvdW50Um9iYmVyeSIsCiAgaW5kVmFyaWFibGVzID0gcmVnLnNzLnZhcnMpICU+JQogIGRwbHlyOjpzZWxlY3QoY3ZJRCA9IGN2SUQsIGNvdW50Um9iYmVyeSwgUHJlZGljdGlvbiwgZ2VvbWV0cnkpCgpyZWcuc3BhdGlhbENWIDwtIGNyb3NzVmFsaWRhdGUoCiAgZGF0YXNldCA9IGZpbmFsX25ldCwKICBpZCA9ICJuYW1lIiwKICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJjb3VudFJvYmJlcnkiLAogIGluZFZhcmlhYmxlcyA9IHJlZy52YXJzKSAlPiUKICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBuYW1lLCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQoKcmVnLnNzLnNwYXRpYWxDViA8LSBjcm9zc1ZhbGlkYXRlKAogIGRhdGFzZXQgPSBmaW5hbF9uZXQsCiAgaWQgPSAibmFtZSIsICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRSb2JiZXJ5IiwKICBpbmRWYXJpYWJsZXMgPSByZWcuc3MudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBuYW1lLCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQoKcmVnLnN1bW1hcnkgPC0gCiAgcmJpbmQoCiAgICBtdXRhdGUocmVnLmN2LCBFcnJvciA9IFByZWRpY3Rpb24gLSBjb3VudFJvYmJlcnksCiAgICAgICAgICAgUmVncmVzc2lvbiA9ICJSYW5kb20gay1mb2xkIENWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLAogICAgbXV0YXRlKHJlZy5zcy5jdiwgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRSb2JiZXJ5LAogICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiUmFuZG9tIGstZm9sZCBDVjogU3BhdGlhbCBQcm9jZXNzIiksCiAgICBtdXRhdGUocmVnLnNwYXRpYWxDViwgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRSb2JiZXJ5LAogICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiU3BhdGlhbCBMT0dPLUNWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLAogICAgbXV0YXRlKHJlZy5zcy5zcGF0aWFsQ1YsIEVycm9yID0gUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwKICAgICAgICAgICBSZWdyZXNzaW9uID0gIlNwYXRpYWwgTE9HTy1DVjogU3BhdGlhbCBQcm9jZXNzIikKICApICU+JSAKICBzdF9zZigpCgpgYGAKCiMjIyBDYWxjdWxhdGluZyBFcnJvcnMgYWNyb3NzIHNwYWNlCgpUaGUgYWNjdXJhY3kgb2YgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgaXMgbWVhc3VyZWQuIE1lYW4gZXJyb3IsIE1BRSwgYW5kIFNEIE1BRSBhcmUgc2hvd24gaW4gdGhlIG1hcHMsIHBsb3RzLCBhbmQgdGFibGUgYmVsb3cuIEl0IGNhbiBiZSBjb25jbHVkZWQgdGhhdCByYW5kb20gay1mb2xkIG1vZGVsIGZ1bmN0aW9ucyBiZXR0ZXIgdGhhbiBzcGF0aWFsIExPR08gbW9kZWwsIGFuZCBpdCBzZWVtcyB0aGF0IGluIHRoZSByYW5kb20gay1mb2xkIG1vZGVsLCBzcGF0aWFsIHByb2Nlc3MgbW9kZWwgd29ya3MgbW9yZSBhY2N1cmF0ZSB0aGFuIGp1c3QgcmlzayBmYWN0b3JzIG1vZGVsLgpgYGB7ciBlcnJvciBhY3Jvc3Mgc3BhY2UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmVycm9yX2J5X3JlZ19hbmRfZm9sZCA8LSAKICByZWcuc3VtbWFyeSAlPiUKICAgIGdyb3VwX2J5KGN2SUQsIFJlZ3Jlc3Npb24pICU+JSAKICAgIHN1bW1hcml6ZShNZWFuX0Vycm9yID0gbWVhbihQcmVkaWN0aW9uIC0gY291bnRSb2JiZXJ5LCBuYS5ybSA9IFQpLAogICAgICAgICAgICAgIE1BRSA9IG1lYW4oYWJzKE1lYW5fRXJyb3IpLCBuYS5ybSA9IFQpLAogICAgICAgICAgICAgIFNEX01BRSA9IG1lYW4oYWJzKE1lYW5fRXJyb3IpLCBuYS5ybSA9IFQpKSAlPiUKICAgICB1bmdyb3VwKCkKCgp2YXJzIDwtIHVuaXF1ZShlcnJvcl9ieV9yZWdfYW5kX2ZvbGQkUmVncmVzc2lvbikKbWFwTGlzdCA8LSBsaXN0KCkKCmZvcihpIGluIHZhcnMpewogIG1hcExpc3RbW2ldXSA8LSAKICAgIGdncGxvdCgpICsKICAgIGdlb21fc2YoZGF0YSA9IGZpbHRlcihlcnJvcl9ieV9yZWdfYW5kX2ZvbGQsIFJlZ3Jlc3Npb24gPT0gaSksIGFlcyhmaWxsPU1BRSksIGNvbG91cj1OQSkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWU9IiIpICsKICAgIGxhYnModGl0bGU9aSwgc2l6ZSA9IDEwKSArCiAgICBtYXBUaGVtZSgpfQoKZG8uY2FsbChncmlkLmFycmFuZ2UsYyhtYXBMaXN0LCBuY29sID0gMiwgdG9wID0gIk1BRSBieSBGb2xkIGFuZCBSZWdyZXNzaW9uIikpCgplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgJT4lCiAgZ2dwbG90KGFlcyhNQUUpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCwgY29sb3VyPSIjMjgzZDNiIiwgZmlsbCA9ICIjYzQ0NTM2IikgKwogIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24pICsgIAogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDApICsgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA4LCBieSA9IDEpKSArIAogIGxhYnModGl0bGU9IkRpc3RyaWJ1dGlvbiBvZiBNQUUiLCBzdWJ0aXRsZSA9ICJrLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiB2cy4gTE9HTy1DViIsCiAgICAgICB4PSJNZWFuIEFic29sdXRlIEVycm9yIiwgeT0iQ291bnQiKSArCiAgcGxvdFRoZW1lKCkKCiBlcnJvcl9ieV9yZWdfYW5kX2ZvbGQgPC0gCiAgcmVnLnN1bW1hcnkgJT4lCiAgICBncm91cF9ieShSZWdyZXNzaW9uKSAlPiUgCiAgICBzdW1tYXJpemUoTWVhbl9FcnJvciA9IG1lYW4oUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICBNQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICBTRF9NQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSkgJT4lCiAgICAgdW5ncm91cCgpCiAKIGVycm9yX2J5X3JlZ19hbmRfZm9sZCAlPiUgCiBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lCiAga2FibGUoKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgICU+JSAKICByb3dfc3BlYygyLCBjb2xvciA9ICIjMjgzZDNiIiwgYmFja2dyb3VuZCA9ICIjZTllY2VmIikgJT4lCiAgcm93X3NwZWMoNCwgY29sb3IgPSAiIzI4M2QzYiIsIGJhY2tncm91bmQgPSAiI2U5ZWNlZiIpIAogCmBgYAoKIyMgRXJyb3JzIGJ5IFJhY2UKClRoZSByYXcgZXJyb3JzIG9mIG1vZGVsIGluIHJhY2UgdHlwZSBhcmUgc2hvd24gaW4gdGhlIGZvbGxvd2luZyB0YWJsZS4gUmFuZG9tIGstZm9sZCBtb2RlbCBzdGlsbCBwZXJmb3JtcyBiZXR0ZXIgdGhhbiBzcGF0aWFsIExPR08gbW9kZWwgaW4gYm90aCBtYWpvcml0eSB3aGl0ZSBhbmQgbWFqb3JpdHkgbm9uLXdoaXRlIHJlZ2lvbnMuIEhvd2V2ZXIsIHRoZSBlcnJvciBvZiBzcGF0aWFsIHByb2Nlc3MgbW9kZWwgaXMgdmVyeSBjbG9zZSB0byBqdXN0IHJpc2sgZmFjdG9ycyBtb2RlbCBpbiB0aGUgY29tcGFyaXNvbi4KCkFsbCB0aGUgbW9kZWxzIHNob3cgZGlmZmVyZW5jZSBiZXR3ZWVuIG1ham9yaXR5IHdoaXRlIGFuZCBtYWpvcml0eSBub24td2hpdCBpbiBlcnJvci4gQ29tcGFyZWQgdG8gbW9kZWwgd2l0aCBqdXN0IHJpc2sgZmFjdG9ycywgdGhlIGVycm9ycyBvZiB0d28gcmVnaW9ucyBhcmUgcmVsZXZhbnRseSBjbG9zZSB0byBlYWNoIG90aGVyIGluIG1vZGVsIHdpdGggc3BhdGlhbCBwcm9jZXNzLCB3aGljaCBtYXkgaW5kaWNhdGUgYSBiZXR0ZXIgZ2VuZXJhbGl6YWJpbGl0eS4KYGBge3IgZ2V0IGFjcywgaW5jbHVkZT1GQUxTRX0KY2Vuc3VzX2FwaV9rZXkoImVlNWJiMzAzZDU2MmExMzRhNWZjMmNkYmM0YWI1M2M4ZDBjYTc2MjkiLCBvdmVyd3JpdGUgPSBUUlVFKQoKdHJhY3RzMTcgPC0gCiAgZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCAKICAgICAgICAgIHZhcmlhYmxlcyA9IGMoIkIwMjAwMV8wMDFFIiwgIyB0b3RhbCBwb3B1bGF0aW9uCiAgICAgICAgICAgICJCMDIwMDFfMDAyRSIgKSwgICMgd2hpdGUgcG9wdWxhdGlvbgogICAgICAgICAgeWVhcj0yMDE3LCBzdGF0ZT0xNywgY291bnR5PTMxLCAKICAgICAgICAgIGdlb21ldHJ5PVRSVUUsIG91dHB1dD0id2lkZSIpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDIyNzEnKSAlPiUgCiAgcmVuYW1lKFRvdGFsUG9wID0gQjAyMDAxXzAwMUUsIAogICAgICAgICBXaGl0ZSA9IEIwMjAwMV8wMDJFKSAlPiUgCiAgbXV0YXRlKHBjdFdoaXRlID0gV2hpdGUvVG90YWxQb3AgLCAKICAgICAgICAgUmFjZSA9IGlmZWxzZShwY3RXaGl0ZSA+IDAuNSwgIk1ham9yaXR5V2hpdGUiLCAiTWFqb3JpdHlOb25XaGl0ZSIpKSAlPiUgCiAgICAuW25laWdoYm9yaG9vZHMsXQpgYGAKCmBgYHtyIGVycm9yIGJ5IHJhY2UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpyZWcuc3VtbWFyeSAlPiUgCiAgc3RfY2VudHJvaWQoKSAlPiUgCiAgc3Rfam9pbih0cmFjdHMxNykgJT4lCiAgbmEub21pdCgpICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBncm91cF9ieShSZWdyZXNzaW9uLCBSYWNlKSAlPiUKICBzdW1tYXJpemUobWVhbi5FcnJvciA9IG1lYW4oRXJyb3IsIG5hLnJtID0gVCkpICU+JQogIHNwcmVhZChSYWNlLCBtZWFuLkVycm9yKSAlPiUKICBrYWJsZShjYXB0aW9uID0gIk1lYW4gRXJyb3IgYnkgbmVpZ2hib3Job29kIHJhY2lhbCBjb250ZXh0IikgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpICAlPiUgCiAgcm93X3NwZWMoMiwgY29sb3IgPSAiIzI4M2QzYiIsIGJhY2tncm91bmQgPSAiI2U5ZWNlZiIpICU+JQogIHJvd19zcGVjKDQsIGNvbG9yID0gIiMyODNkM2IiLCBiYWNrZ3JvdW5kID0gIiNlOWVjZWYiKSAKCmBgYAoKIyBEZW5zaXR5IHZzIHByZWRpY3Rpb25zCgpUaGUgZm9sbG93aW5nIHBsb3QgaXMgdGhlIGtlcm5lbCBkZW5zaXR5IG9mIHJvYmJlcmllcyBpbiAyMDE3LiBUaGUgcmVnaW9ucyB3aXRoIGhpZ2gga2VybmVsIGRlbnNpdHkgaGF2ZSBhIGhpZ2hlciBwcm9iYWJpbGl0eSBvZiByb2JiZXJpZXMgaW4gdGhlIG5leHQgeWVhci4KYGBge3IgMjAxNyByb2JiZXJ5LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpyb2JfcHBwIDwtIGFzLnBwcChzdF9jb29yZGluYXRlcyhyb2JiZXJ5KSwgVyA9IHN0X2Jib3goZmluYWxfbmV0KSkKcm9iX0tELjEwMDAgPC0gc3BhdHN0YXQuZXhwbG9yZTo6ZGVuc2l0eS5wcHAocm9iX3BwcCwgMTAwMCkKCnJvYl9LRC5kZiA8LSByYmluZCgKICBtdXRhdGUoZGF0YS5mcmFtZShyYXN0ZXJUb1BvaW50cyhtYXNrKHJhc3Rlcihyb2JfS0QuMTAwMCksIGFzKG5laWdoYm9yaG9vZHMsICdTcGF0aWFsJykpKSksIExlZ2VuZCA9ICIxMDAwIEZ0LiIpKSAKCnJvYl9LRC5kZiRMZWdlbmQgPC0gZmFjdG9yKHJvYl9LRC5kZiRMZWdlbmQsIGxldmVscyA9IGMoIjEwMDAgRnQuIikpCgphcy5kYXRhLmZyYW1lKHJvYl9LRC4xMDAwKSAlPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJ4IiwgInkiKSwgY3JzID0gc3RfY3JzKGZpbmFsX25ldCkpICU+JQogIGFnZ3JlZ2F0ZSguLCBmaW5hbF9uZXQsIG1lYW4pICU+JQogICBnZ3Bsb3QoKSArCiAgICAgZ2VvbV9zZihhZXMoZmlsbD12YWx1ZSkpICsKICAgICBnZW9tX3NmKGRhdGEgPSBzYW1wbGVfbihyb2JiZXJ5LCAxNTAwKSwgc2l6ZSA9IC41LCBjb2xvciA9ICIjMjgzZDNiIikgKwogICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIkRlbnNpdHkiKSArCiAgICAgbGFicyh0aXRsZSA9ICJLZXJuZWwgRGVuc2l0eSBvZiAyMDIwIFJvYmJlcnkiKSArCiAgICAgbWFwVGhlbWUodGl0bGVfc2l6ZSA9IDE0KQoKYGBgCkNvbXBhcmVkIHRvIHByZWRpY3Rpb24gbWFkZSBieSBrZXJuZWwgZGVuc2l0eSBhbmFseXNpcywgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgbWFrZXMgYSBtb3JlIGFjY3VyYXRlIHByZWRpY3Rpb24gb24gdGhlIGdlb2dyYXBoaWMgbGV2ZWwuIFJlZ3Jlc3Npb24gbW9kZWwgd29ya3MgbW9yZSBwcmVjaXNlbHkgaW4gbW9zdCBwYXJ0cyBvZiB0aGUgY2l0eSwgZXNwZWNpYWxseSB0aGUgc291dGhlcm4gcGFydCwgYnV0IHNlZW1zIHRvIHVuZGVyZXN0aW1hdGUgdGhlIHJvYmJlcnkgcmFuayBpbiBub3J0aCBDaGljYWdvIGF0IHRoZSBzYW1lIHRpbWUuIE1lYW53aGlsZSwgdGhlIHJlc3VsdCBvZiBrZXJuZWwgZGVuc2l0eSBhbmFseXNpcyBpcyBtb3JlIHJlbGlhYmxlIGluIG5vcnRoZWFzdCBhcmVhLCB3aGVyZSByZWdyZXNzaW9uIG1vZGVsIGZhaWxzIHRvIHJhbmsgYSBoaWdoZXIgbGV2ZWwgb2Ygcm9iYmVyeSBwb3NzaWJpbGl0eS4KYGBge3IgS0QgYW5kIFByZWRpY3Rpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJvYmJlcnkyMSA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9QdWJsaWMtU2FmZXR5L0NyaW1lcy0yMDIxL2R3bWUtdDk2YyIpICU+JSAKICBmaWx0ZXIoUHJpbWFyeS5UeXBlID09ICJST0JCRVJZIiAmIERlc2NyaXB0aW9uID09ICJBUk1FRCAtIEhBTkRHVU4iKSAlPiUKICBtdXRhdGUoeCA9IGdzdWIoIlsoKV0iLCAiIiwgTG9jYXRpb24pKSAlPiUKICBzZXBhcmF0ZSh4LGludG89IGMoIlkiLCJYIiksIHNlcD0iLCIpICU+JQogIG11dGF0ZShYID0gYXMubnVtZXJpYyhYKSwKICAgICAgICAgWSA9IGFzLm51bWVyaWMoWSkpICU+JSAKICBuYS5vbWl0ICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSAlPiUKICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyMjcxJykgJT4lIAogIGRpc3RpbmN0KCkgJT4lCiAgLltmaXNobmV0LF0KCnJvYl9LREVfc3VtIDwtIGFzLmRhdGEuZnJhbWUocm9iX0tELjEwMDApICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIngiLCAieSIpLCBjcnMgPSBzdF9jcnMoZmluYWxfbmV0KSkgJT4lCiAgYWdncmVnYXRlKC4sIGZpbmFsX25ldCwgbWVhbikgCmtkZV9icmVha3MgPC0gY2xhc3NJbnRlcnZhbHMocm9iX0tERV9zdW0kdmFsdWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSA1LCAiZmlzaGVyIikKcm9iX0tERV9zZiA8LSByb2JfS0RFX3N1bSAlPiUKICBtdXRhdGUobGFiZWwgPSAiS2VybmVsIERlbnNpdHkiLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gY2xhc3NJbnQ6OmZpbmRDb2xzKGtkZV9icmVha3MpLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNSB+ICI1dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNCB+ICI0dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMyB+ICIzcmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMiB+ICIybmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMSB+ICIxc3QiKSkgJT4lCiAgY2JpbmQoCiAgICBhZ2dyZWdhdGUoCiAgICAgIGRwbHlyOjpzZWxlY3Qocm9iYmVyeTIxKSAlPiUgbXV0YXRlKHJvYmJlcnlDb3VudCA9IDEpLCAuLCBzdW0pICU+JQogICAgbXV0YXRlKHJvYmJlcnlDb3VudCA9IHJlcGxhY2VfbmEocm9iYmVyeUNvdW50LCAwKSkpICU+JQogIGRwbHlyOjpzZWxlY3QobGFiZWwsIFJpc2tfQ2F0ZWdvcnksIHJvYmJlcnlDb3VudCkKCgptbF9icmVha3MgPC0gY2xhc3NJbnRlcnZhbHMocmVnLnNzLnNwYXRpYWxDViRQcmVkaWN0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gNSwgImZpc2hlciIpCnJvYl9yaXNrX3NmIDwtCiAgcmVnLnNzLnNwYXRpYWxDViAlPiUKICBtdXRhdGUobGFiZWwgPSAiUmlzayBQcmVkaWN0aW9ucyIsCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPWNsYXNzSW50OjpmaW5kQ29scyhtbF9icmVha3MpLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNSB+ICI1dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNCB+ICI0dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMyB+ICIzcmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMiB+ICIybmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMSB+ICIxc3QiKSkgJT4lCiAgY2JpbmQoCiAgICBhZ2dyZWdhdGUoIAogICAgICBkcGx5cjo6c2VsZWN0KHJvYmJlcnkyMSkgJT4lIG11dGF0ZShyb2JiZXJ5Q291bnQgPSAxKSwgLiwgc3VtKSAlPiUKICAgICAgbXV0YXRlKHJvYmJlcnlDb3VudCA9IHJlcGxhY2VfbmEocm9iYmVyeUNvdW50LCAwKSkpICU+JQogIGRwbHlyOjpzZWxlY3QobGFiZWwsUmlza19DYXRlZ29yeSwgcm9iYmVyeUNvdW50KQoKCnJiaW5kKHJvYl9LREVfc2YsIHJvYl9yaXNrX3NmKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWxhYmVsLCAtUmlza19DYXRlZ29yeSwgLWdlb21ldHJ5KSAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gUmlza19DYXRlZ29yeSksIGNvbG91ciA9IE5BKSArCiAgICBnZW9tX3NmKGRhdGEgPSBzYW1wbGVfbihyb2JiZXJ5MjEsIDMwMDAsIHJlcGxhY2UgPSBUUlVFKSwgc2l6ZSA9IC41LCBjb2xvdXIgPSAiIzI4M2QzYiIpICsKICAgIGZhY2V0X3dyYXAofmxhYmVsLCApICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUpICsKICAgIGxhYnModGl0bGU9IkNvbXBhcmlzb24gb2YgS2VybmVsIERlbnNpdHkgYW5kIFJpc2sgUHJlZGljdGlvbnMiLAogICAgICAgICBzdWJ0aXRsZT0iMjAyMCByb2JiZXJ5IHJpc2sgcHJlZGljdGlvbnM7IDIwMjEgcm9iYmVyeSIpICsKICAgIG1hcFRoZW1lKHRpdGxlX3NpemUgPSAxNCkKYGBgCkFzIGNhbiBiZSBzZWVuIGluIHRoZSBjaGFydCBiZWxvdywgdGhlcmUncyBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcmVzdWx0IHByb2R1Y2VkIGJ5IGtlcm5lbCBkZW5zaXR5IGFuYWx5c2lzIGFuZCB0aGUgcHJlZGljdGlvbiBtYWRlIHJlZ3Jlc3Npb24gbW9kZWwuIEtlcm5lbCBkZW5zaXR5IGFuYWx5c2lzIHByZWRpY3RzIG1vcmUgY2FzZXMgaW4gaGlnaCByYW5rIGdyb3VwcyAoM3JkLCA0dGggYW5kIDV0aCksIGVzcGVjaWFsbHkgaW4gdGhlIDR0aCByYW5rLCB3aGlsZSByZWdyZXNzaW9uIG1vZGVsIHByZWRpY3RzIG1vcmUgY2FzZXMgaW4gbG93IHJhbmsgZ3JvdXBzICgxc3QgYW5kIDJuZCksIGVzcGVjaWFsbHkgaW4gdGhlIDFzdCByYW5rLiBJbiBnZW5lcmFsLCBrZXJuZWwgZGVuc2l0eSBhbmFseXNpcyBwcmVkaWN0cyBhIGluY3JlYXNlIHRyZW5kIGluIHJvYmJlcnkgY2FzZXMgYW5kIGNvdmVyYWdlLCBhcyByZWdyZXNzaW9uIG1vZGVsIHN1Z2dlc3RzIHRoZXJlIHNob3VsZCBiZSBhIGRlY2xpbmVkIHRyZW5kIGluIDIwMjEgb24gdGhlIGNvbnRyYXJ5LgpgYGB7ciBoaXN0Z3JhbSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcmJpbmQocm9iX0tERV9zZiwgcm9iX3Jpc2tfc2YpICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBuYS5vbWl0KCkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWxhYmVsLCAtUmlza19DYXRlZ29yeSkgJT4lCiAgZ3JvdXBfYnkobGFiZWwsIFJpc2tfQ2F0ZWdvcnkpICU+JQogIHN1bW1hcml6ZShjb3VudFJvYmJlcnkgPSBzdW0oVmFsdWUpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ3JvdXBfYnkobGFiZWwpICU+JQogIG11dGF0ZShQY250X29mX3Rlc3Rfc2V0X2NyaW1lcyA9IGNvdW50Um9iYmVyeSAvIHN1bShjb3VudFJvYmJlcnkpKSAlPiUKICAgIGdncGxvdChhZXMoUmlza19DYXRlZ29yeSxQY250X29mX3Rlc3Rfc2V0X2NyaW1lcykpICsKICAgICAgZ2VvbV9iYXIoYWVzKGZpbGw9bGFiZWwpLCBwb3NpdGlvbj0iZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpICsKICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKGRpc2NyZXRlID0gVFJVRSwgbmFtZSA9ICJNb2RlbCIpICsKICAgICAgbGFicyh0aXRsZSA9ICJSaXNrIHByZWRpY3Rpb24gdnMuIEtlcm5lbCBkZW5zaXR5LCAyMDIxIHJvYmJlcnkiLAogICAgICAgICAgIHkgPSAiJSBvZiBUZXN0IFNldCBSb2JiZXJ5IChwZXIgbW9kZWwpIiwKICAgICAgICAgICB4ID0gIlJpc2sgQ2F0ZWdvcnkiKSArCiAgdGhlbWVfYncoKSArCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMC41KSkKYGBgCgojIENvbmNsdXNpb24KCkluIHRoaXMgcHJvamVjdCwgZ2Vvc3BhdGlhbCBmZWF0dXJlcyBhcmUgZXhhbWluZWQgYW5kIGluY2x1ZGVkIGluIHRoZSByaXNrIHJlZ3Jlc3Npb24gbW9kZWwgdG8gcmV2ZWFsIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBzcGF0aWFsIGZlYXR1cmVzIGFuZCB0aGUgY3JpbWUgYXR0ZW1wdHMsIGVzcGVjaWFsbHkgcm9iYmVyeSwgaW4gQ2hpY2Fnby4gQWx0aG91Z2ggdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsIG1heSBub3QgYmUgcHJlY2lzZSBlbm91Z2ggaW4gc29tZSBwYXJ0aWN1bGFyIGFyZWFzLCB0aGlzIHByb2plY3Rpb24gY2FuIGV4cGxhaW4gdGhlIGN1cnJlbnQgY3JpbWUgcGF0dGVybiBpbiBDaGljYWdvIHRvIHNvbWUgZXh0ZW50LiAKCkhvd2V2ZXIsIEkgd2lsbCBwcm9iYWJseSBub3QgcmVjb21tZW5kIHRoZSByZWdyZXNzaW9uIG1vZGVsIGZvciBwcm9kdWN0aW9uIHVzZS4gRmlyc3RseSwgdGFraW5nIHRoZSBmdXR1cmUgcHJlZGljdGlvbiBhcyBhbiBleGFtcGxlLCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgaXMgbm90IGlkZWFsIGluIG5vcnRoZXJuIHBhcnQgb2YgQ2hpY2FnbzogY29tcGFyZWQgdG8ga2VybmVsIGRlbnNpdHkgYW5hbHlzaXMsIGl0IG5vdGFibHkgdW5kZXJlc3RpbWF0ZXMgcm9iYmVyaWVzIGluIHRoZSBub3J0aGVhc3QuIE1vcmVvdmVyLCB0aGUgTUFFIGFuZCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIE1BRSBhcmUgdG9vIHNtYWxsLCB3aGljaCBtYXkgaW5kaWNhdGUgdGhhdCB0aGUgcmVncmVzc2lvbiBtb2RlbCBpcyBvdmVyZml0dGluZy4gVGhpcyB3aWxsIHJlc3VsdCBpbiB0aGUgZGVjcmVhc2UgaW4gZ2VuZXJhbGl6YWJpbGl0eSwgYW5kIHRodXMgcHJvZHVjaW5nIG1vcmUgZXJyb3JzIGluIHByZWRpY3Rpb24uIEFkZGl0aW9uYWxseSwgdGhlIHNlbGVjdGVkIGluZGVwZW5kZW50IHZhcmlhYmxlcyBjYW4gb25seSBwYXJ0bHkgcmVwcmVzZW50IHNvbWUgZmFjdG9ycyBkcml2aW5nIHJvYmJlcmllcyBpbiBDaGljYWdvLiBPdGhlciBlY29ub21pYyBhbmQgZGVtb2dyYXBoaWMgZmFjdG9ycyBhcmUgZGlzbWlzc2VkIGluIHRoaXMgbW9kZWwsIGxlYWRpbmcgdG8gYW4gaW5jb21wcmVoZW5zaXZlIGludGVycHJldGF0aW9uIG9mIHJvYmJlcmllcy4gRnVydGhlcm1vcmUsIHRoZSBiYXNlIGRhdGFzZXQgaXMgb25seSBhYm91dCAyMDE3LCB3aGljaCBpcyBub3QgcmVwcmVzZW50YXRpdmUgZW5vdWdoLCBhbmQgbW9yZSBkYXRhIGFib3V0IG90aGVyIHllYXJzIHNob3VsZCBiZSB0YWtlbiBpbnRvIGNvbnNpZGVyYXRpb24gdG8gcHJvZHVjZSBhIG1vcmUgcmVsaWFibGUgbW9kZWwuIAoKRHVlIHRvIHRoZXNlIHJlYXNvbnMsIEkgd291bGQgbGVzcyBsaWtlbHkgdG8gcmVjb21tZW5kIHRoaXMgcmVncmVzc2lvbiBtb2RlbCBmb3IgcHJvZHVjdGlvbiB1c2UsIGJ1dCBJIHdpbGwgc3VnZ2VzdCB0aGF0IHRoZSBtb2RlbCBjYW4gYmUgY29uc2lkZXJlZCBhcyBhIHJlZmVyZW5jZSBpbiBmaXR0aW5nIHN1aXRhYmxlIG1vZGVsIGZvciBwb2xpY2UgcHJhY3RpY2UsIHNpbmNlIHNvbWUgdmFyaWFibGVzIGluIHRoZSByZWdyZXNzaW9uIG1vZGVsIHNob3cgc2lnbmlmaWNhbnQgcmVsYXRpb25zaGlwcyB3aXRoIHJvYmJlcmllcyBhbmQgdGhlIG1vZGVsIG1ha2VzIGEgd2VsbCBwcmVkaWN0aW9uIGluIHNvbWUgcGFydHMgb2YgQ2hpY2FnbyBpbiBwcmVkaWN0aW9uLgo=